package com.navinfo.tripanalysis.common.pojo;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.LinkedHashMap;
import java.util.LinkedList;

/**
 * 输入一组long类型，在Linked中倒序查找匹配最相近的key，要求key是有序的.<br>
 * 例如：输入 10,20,30,40,50,60,70,80,90,100; 匹配 42，会匹配到40上.<br>
 * @author 水哥
 */
public class NumNearlyMap<K extends Long, V> extends LinkedHashMap<Long, V> {
    private static Logger logger = LoggerFactory.getLogger(NumNearlyMap.class);

    /**
     * LinkedHashMap最大长度，默认为10.
     */
    private long linkedMaxLength = 10;

    /**
     * 如果只有一个点，那么向上扩展一个区间.
     */
    private long moreThanLast = 30000;

    /**
     * keyQueue
     */
    private LinkedList<Long> keyQueue = new LinkedList<>();

    /**
     * 判断当前事件是否在start 和 end+moreThanLast之间.
     *
     * @param firstNode 队列头时间
     * @param lastNode  队列尾时间
     * @return boolean 是否在范围内
     */
    private boolean inTheRange(long firstNode, long lastNode, long currentTime) {
        return currentTime >= firstNode && currentTime <= lastNode + moreThanLast;
    }

    /**
     * 倒序查找匹配最相近节点，如果匹配失败返回null.
     *
     * @param currentTime 需要匹配的时间戳
     * @return T，如果无匹配结果，返回null
     */
    public V match(long currentTime) {
        if (super.isEmpty()) {
            return null;
        }
        //判断是否在区间内.
        if (!inTheRange(keyQueue.getFirst(), keyQueue.getLast(), currentTime)) {
            logger.debug("■ currentTime not in the range!start:[" + keyQueue.getFirst() + "]," +
                    "end:[" + keyQueue.getLast() + "],current:[" + currentTime + "]");
            return null;
        }
        int queueSize = keyQueue.size();
        //如果在区间内，队列长度是1，那么直接返回
        if (queueSize == 1) {
            return super.get(keyQueue.getFirst());
        }
        Long[] keys = keyQueue.toArray(new Long[queueSize]);
        //如果在区间内，队列长度不是1，那么循环比较，取最接近的值,如果正好是中间值,向大匹配.
        for (int i = queueSize - 1; i >= 0; i--) {
            long currentNode = keys[i];
            if (currentTime <= currentNode) {
                continue;
            }
            if (i == queueSize - 1) {
                return super.get(currentNode);
            }
            long preNode = keys[i + 1];
            return Math.abs(currentTime - preNode) <= Math.abs(currentTime - currentNode)
                    ? super.get(preNode) : super.get(currentNode);
        }
        return null;
    }

    /**
     * 重写Put方法，当长度等于linkedMaxLength时，remove掉第一个值.
     *
     * @param key   key with which the specified value is to be associated
     * @param value value to be associated with the specified key
     * @return the previous value associated with <tt>key</tt>, or
     * <tt>null</tt> if there was no mapping for <tt>key</tt>.
     * (A <tt>null</tt> return can also indicate that the map
     * previously associated <tt>null</tt> with <tt>key</tt>.)
     */
    @Override
    public V put(Long key, V value) {
        if (super.size() == linkedMaxLength) {
            super.remove(keyQueue.pollFirst());
        }
        if (!keyQueue.contains(key)) {
            keyQueue.add(key);
        }
        return super.put(key, value);
    }

    public void setLinkedMaxLength(long linkedMaxLength) {
        this.linkedMaxLength = linkedMaxLength;
    }

    public void setMoreThanLast(long moreThanLast) {
        this.moreThanLast = moreThanLast;
    }
}
